#include "PanoramaDownloader.h"

#include <boost/scoped_array.hpp>
#include <sstream>
#include <cmath>
#include <iostream>
#include <curlpp/cURLpp.hpp>
#include <curlpp/Easy.hpp>
#include <curlpp/Multi.hpp>
#include <curlpp/Options.hpp>
#include <curlpp/Exception.hpp>
#include "CompositeImageInterface.h"
#include "SimpleImage.h"
#include "Color.h"

using namespace std;
using boost::scoped_array;

PanoramaDownloader::PanoramaDownloader(string id, string server,
    int timeout, string user_agent, int world_width, int world_height,
    int tile_width, int tile_height, int zoom)
    : id_(id),
      server_(server),
      timeout_(timeout),
      user_agent_(user_agent),
      world_width_(world_width),
      world_height_(world_height),
      tile_width_(tile_width),
      tile_height_(tile_height),
      zoom_(zoom) {}

int PanoramaDownloader::download(CompositeImageInterface* output) const {
  // Cleans up URL resources.
  curlpp::Cleanup cleanup;

  int cols = ceil((double)world_width_ / tile_width_);
  int rows = ceil((double)world_height_ / tile_height_);

  // HTTP requests for every tile.
  // curlpp::Easy must be declared before curlpp:Multi for destructors.
  scoped_array<curlpp::Easy> requests(new curlpp::Easy[rows * cols]);
  // Multiple request handler.
  curlpp::Multi multi;

  // Streams to download tile data into.
  scoped_array<std::stringstream> streams(new stringstream[rows * cols]);

  for (int i = 0; i < rows; i++) {
    for (int j = 0; j < cols; j++) {
      // Set up a request for each tile.
      curlpp::Easy* request = &requests[i * cols + j];

      // Construct the URL with HTTP GET arguments.
      string url(tileUrl(i, j));
      request->setOpt(new curlpp::options::Url(url));
      // Disable verbose mode.
      request->setOpt(new curlpp::options::Verbose(false));
      // Set the user agent string.
      request->setOpt(new curlpp::options::UserAgent(user_agent_));
      // Set the output stream.
      stringstream* stream = &streams[i * cols + j];
      request->setOpt(new curlpp::options::WriteStream(stream));

      // Add the request.
      multi.add(request);
    }
  }

  // Start performing requests.
  int num_left;
  while (!multi.perform(&num_left)) {}

  while (num_left != 0) {
    // File descriptors for dealing with streams.
    fd_set fdread;
    FD_ZERO(&fdread);
    fd_set fdwrite;
    FD_ZERO(&fdwrite);
    fd_set fdexcept;
    FD_ZERO(&fdexcept);
    int maxfd = -1;

    // Timeout for select().
    struct timeval timeout;
    timeout.tv_sec = timeout_ / 1000;
    timeout.tv_usec = timeout_ % 1000;

    // Initialise cURL's file descriptors.
    multi.fdset(&fdread, &fdwrite, &fdexcept, &maxfd);

    // Block on receiving data.
    int result = select(maxfd + 1, &fdread, &fdwrite, &fdexcept, &timeout);
    switch (result) {
      case -1:
        // select() error.
        num_left = 0;
        cerr << "Error: select() returned -1" << endl;
        break;
      case 0:
        // Timeout.
        cerr << "HTTP request timed out" << endl;
        break;
      default:
        // One or more file descriptors have data to read or write.
        while (!multi.perform(&num_left)) {}
        break;
    }
  }

  // Compile the tiles into a composite image.
  output->reset(world_width_, world_height_, tile_width_, tile_height_,
      Color(0, 0, 0));

  for (int i = 0; i < rows; i++) {
    for (int j = 0; j < cols; j++) {
      // Read data from stream.
      output->loadTileFromStream(i, j, streams[i * cols + j]);
    }
  }

  return 0;
}

// Constructs the HTTP GET arguments.
string PanoramaDownloader::tileUrl(int i, int j) const {
  ostringstream ss;

  ss << server_ << "?output=tile&panoid=" << id_ << "&zoom=" << zoom_;
  ss << "&x=" << j << "&y=" << i;

  return ss.str();
}

